#
## Licensed to the .NET Foundation under one or more agreements.
## The .NET Foundation licenses this file to you under the MIT license.
#
#
#USAGE:
#Add Events: modify <root>src/vm/ClrEtwAll.man
#Look at the Code in  <root>/src/scripts/genLttngProvider.py for using subroutines in this file
#

# Python 2 compatibility
from __future__ import print_function

import os
import xml.dom.minidom as DOM
from utilities import open_for_update, parseInclusionList

class RuntimeFlavor:
    def __init__(self, runtime):
        if runtime.lower() == "coreclr":
            self.coreclr = True
            self.mono = False
            self.nativeaot = False
        elif runtime.lower() == "mono":
            self.coreclr = False
            self.mono = True
            self.nativeaot = False
        elif runtime.lower() == "nativeaot":
            self.coreclr = False
            self.mono = False
            self.nativeaot = True
        else:
            self.coreclr = True
            self.mono = False
            self.nativeaot = False

stdprolog="""
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

/******************************************************************

DO NOT MODIFY. AUTOGENERATED FILE.
This file is generated using the logic from <root>/src/scripts/genEventing.py

******************************************************************/
"""

lindent = "    "

coreCLRPalDataTypeMapping={
    #constructed types
    "win:null"          :" ",
    "win:Int64"         :"const int64_t",
    "win:ULong"         :"const ULONG",
    "win:count"         :"*",
    "win:Struct"        :"const void",
    #actual spec
    "win:GUID"          :"const GUID",
    "win:AnsiString"    :"LPCSTR",
    "win:UnicodeString" :"PCWSTR",
    "win:Double"        :"const double",
    "win:Int32"         :"const signed int",
    "win:Boolean"       :"const BOOL",
    "win:UInt64"        :"const uint64_t",
    "win:UInt32"        :"const unsigned int",
    "win:UInt16"        :"const unsigned short",
    "win:UInt8"         :"const unsigned char",
    "win:Pointer"       :"const void*",
    "win:Binary"        :"const BYTE",
}

coreCLREventPipeDataTypeMapping={
    "BOOL"              : "BOOL",
    "LPCGUID"           : "LPCGUID",
    "UCHAR"             : "UCHAR",
    "ULONG"             : "ULONG",
    "ULONGLONG"         : "ULONGLONG",
    "WCHAR"             : "WCHAR",
    "BYTE"              : "BYTE",
}

monoPalDataTypeMapping={
    #constructed types
    "win:null"          :" ",
    "win:Int64"         :"const int64_t",
    "win:ULong"         :"const uint32_t",
    "win:count"         :"*",
    "win:Struct"        :"const void",
    #actual spec
    "win:GUID"          :"const uint8_t",
    "win:AnsiString"    :"const char*",
    "win:UnicodeString" :"const ep_char8_t*",
    "win:Double"        :"const double",
    "win:Int32"         :"const int32_t",
    "win:Boolean"       :"const bool",
    "win:UInt64"        :"const uint64_t",
    "win:UInt32"        :"const uint32_t",
    "win:UInt16"        :"const uint16_t",
    "win:UInt8"         :"const uint8_t",
    "win:Pointer"       :"const void*",
    "win:Binary"        :"const uint8_t",
}

monoEventPipeDataTypeMapping={
    "BOOL"              : "bool",
    "LPCGUID"           : "const uint8_t*",
    "UCHAR"             : "uint8_t",
    "ULONG"             : "uint32_t",
    "ULONGLONG"         : "uint64_t",
    "WCHAR"             : "wchar_t",
    "BYTE"              : "uint8_t",
}

aotPalDataTypeMapping={
    #constructed types
    "win:null"          :" ",
    "win:Int64"         :"const int64_t",
    "win:ULong"         :"const ULONG",
    "win:count"         :"*",
    "win:Struct"        :"const void",
    #actual spec
    "win:GUID"          :"const GUID",
    "win:AnsiString"    :"LPCSTR",
    "win:UnicodeString" :"const WCHAR*",
    "win:Double"        :"const double",
    "win:Int32"         :"const signed int",
    "win:Boolean"       :"const BOOL",
    "win:UInt64"        :"const uint64_t",
    "win:UInt32"        :"const unsigned int",
    "win:UInt16"        :"const unsigned short",
    "win:UInt8"         :"const unsigned char",
    "win:Pointer"       :"const void*",
    "win:Binary"        :"const BYTE",
}

aotEventPipeDataTypeMapping={
    "BOOL"              : "BOOL",
    "LPCGUID"           : "const GUID *",
    "UCHAR"             : "UCHAR",
    "ULONG"             : "ULONG",
    "ULONGLONG"         : "ULONGLONG",
    "WCHAR"             : "WCHAR",
    "BYTE"              : "BYTE",
}

def getEventPipeDataTypeMapping(runtimeFlavor):
    if runtimeFlavor.coreclr:
        return coreCLREventPipeDataTypeMapping
    elif runtimeFlavor.mono:
        return monoEventPipeDataTypeMapping
    elif runtimeFlavor.nativeaot:
        return aotEventPipeDataTypeMapping

def getPalDataTypeMapping(runtimeFlavor):
    if runtimeFlavor.coreclr:
        return coreCLRPalDataTypeMapping
    elif runtimeFlavor.mono:
        return monoPalDataTypeMapping
    elif runtimeFlavor.nativeaot:
        return aotPalDataTypeMapping

def includeProvider(providerName, runtimeFlavor):
    if (runtimeFlavor.coreclr or runtimeFlavor.nativeaot) and providerName == "Microsoft-DotNETRuntimeMonoProfiler":
        return False
    elif runtimeFlavor.nativeaot and providerName == "Microsoft-Windows-DotNETRuntimeStress":
        return False
    else:
        return True

def includeEvent(inclusionList, providerName, eventName):
    if len(inclusionList) == 0:
        return True
    if providerName in inclusionList and eventName in inclusionList[providerName]:
        return True
    elif providerName in inclusionList and "*" in inclusionList[providerName]:
        return True
    elif "*" in inclusionList and eventName in inclusionList["*"]:
        return True
    elif "*" in inclusionList and "*" in inclusionList["*"]:
        return True
    else:
        return False

def getCoreCLRMonoNativeAotTypeAdaptionDefines():
    return """
#ifndef W
#define W(str) L##str
#endif
#ifndef SL
#define SL(str) str
#endif
#ifndef ERROR_SUCCESS
#define ERROR_SUCCESS 0L
#endif
#ifndef ERROR_WRITE_FAULT
#define ERROR_WRITE_FAULT 29L
#endif
"""

# A Template represents an ETW template can contain 1 or more AbstractTemplates
# The AbstractTemplate contains FunctionSignature
# FunctionSignature consist of FunctionParameter representing each parameter in it's signature

def getParamSequenceSize(paramSequence, estimate):
    total = 0
    pointers = 0
    for param in paramSequence:
        if param == "win:Int64":
            total += 8
        elif param == "win:ULong":
            total += 4
        elif param == "GUID":
            total += 16
        elif param == "win:Double":
            total += 8
        elif param == "win:Int32":
            total += 4
        elif param == "win:Boolean":
            total += 4
        elif param == "win:UInt64":
            total += 8
        elif param == "win:UInt32":
            total += 4
        elif param == "win:UInt16":
            total += 2
        elif param == "win:UInt8":
            total += 1
        elif param == "win:Pointer":
            if estimate:
                total += 8
            else:
                pointers += 1
        elif param == "win:Binary":
            total += 1
        elif estimate:
            if param == "win:AnsiString":
                total += 32
            elif param == "win:UnicodeString":
                total += 64
            elif param == "win:Struct":
                total += 32
        else:
            raise Exception("Don't know size for " + param)

    if estimate:
        return total

    return total, pointers


class Template:
    def __repr__(self):
        return "<Template " + self.name + ">"

    def __init__(self, templateName, fnPrototypes, dependencies, structSizes, arrays):
        self.name = templateName
        self.signature = FunctionSignature()
        self.structs = structSizes
        self.arrays = arrays

        for variable in fnPrototypes.paramlist:
            for dependency in dependencies[variable]:
                if not self.signature.getParam(dependency):
                    self.signature.append(dependency, fnPrototypes.getParam(dependency))

    def getFnParam(self, name):
        return self.signature.getParam(name)

    @property
    def num_params(self):
        return len(self.signature.paramlist)

    @property
    def estimated_size(self):
        total = getParamSequenceSize((self.getFnParam(paramName).winType for paramName in self.signature.paramlist), True)

        if total < 32:
            total = 32
        elif total > 1024:
            total = 1024

        return total



class FunctionSignature:
    def __repr__(self):
        return ", ".join(self.paramlist)

    def __init__(self):
        self.LUT       = {} # dictionary of FunctionParameter
        self.paramlist = [] # list of parameters to maintain their order in signature

    def append(self,variable,fnparam):
        self.LUT[variable] = fnparam
        self.paramlist.append(variable)

    def getParam(self,variable):
        return self.LUT.get(variable)

    def getLength(self):
        return len(self.paramlist)

class FunctionParameter:
    def __repr__(self):
        return self.name

    def __init__(self,winType,name,count,prop):
        self.winType  = winType   #ETW type as given in the manifest
        self.name     = name      #parameter name as given in the manifest
        self.prop     = prop      #any special property as determined by the manifest and developer
        #self.count               #indicates if the parameter is a pointer
        if  count == "win:null":
            self.count    = "win:null"
        elif count or winType == "win:GUID" or count == "win:count":
        #special case for GUIDS, consider them as structs
            self.count    = "win:count"
        else:
            self.count    = "win:null"


def getTopLevelElementsByTagName(node,tag):
    dataNodes = []
    for element in node.getElementsByTagName(tag):
        if element.parentNode == node:
            dataNodes.append(element)

    return dataNodes

ignoredXmlTemplateAttribes = frozenset(["map","outType"])
usedXmlTemplateAttribes    = frozenset(["name","inType","count", "length"])

def parseTemplateNodes(templateNodes):

    #return values
    allTemplates           = {}

    for templateNode in templateNodes:
        structCounts = {}
        arrays = {}
        templateName    = templateNode.getAttribute('tid')
        var_Dependencies = {}
        fnPrototypes    = FunctionSignature()
        dataNodes       = getTopLevelElementsByTagName(templateNode,'data')

        # Validate that no new attributes has been added to manifest
        for dataNode in dataNodes:
            nodeMap = dataNode.attributes
            for attrib in nodeMap.values():
                attrib_name = attrib.name
                if attrib_name not in ignoredXmlTemplateAttribes and attrib_name not in usedXmlTemplateAttribes:
                    raise ValueError('unknown attribute: '+ attrib_name + ' in template:'+ templateName)

        for dataNode in dataNodes:
            variable = dataNode.getAttribute('name')
            wintype = dataNode.getAttribute('inType')

            #count and length are the same
            wincount  = dataNode.getAttribute('count')
            winlength = dataNode.getAttribute('length');

            var_Props = None
            var_dependency = [variable]
            if  winlength:
                if wincount:
                    raise Exception("both count and length property found on: " + variable + "in template: " + templateName)
                wincount = winlength

            if (wincount.isdigit() and int(wincount) ==1):
                wincount = ''

            if  wincount:
                if (wincount.isdigit()):
                    var_Props = wincount
                elif  fnPrototypes.getParam(wincount):
                    var_Props = wincount
                    var_dependency.insert(0, wincount)
                    arrays[variable] = wincount

            #construct the function signature

            if  wintype == "win:GUID":
                var_Props = "sizeof(GUID)/sizeof(int)"

            var_Dependencies[variable] = var_dependency
            fnparam        = FunctionParameter(wintype,variable,wincount,var_Props)
            fnPrototypes.append(variable,fnparam)

        structNodes = getTopLevelElementsByTagName(templateNode,'struct')

        for structToBeMarshalled in structNodes:
            structName   = structToBeMarshalled.getAttribute('name')
            countVarName = structToBeMarshalled.getAttribute('count')

            assert(countVarName == "Count")
            assert(countVarName in fnPrototypes.paramlist)
            if not countVarName:
                raise ValueError("Struct '%s' in template '%s' does not have an attribute count." % (structName, templateName))

            names = [x.attributes['name'].value for x in structToBeMarshalled.getElementsByTagName("data")]
            types = [x.attributes['inType'].value for x in structToBeMarshalled.getElementsByTagName("data")]

            structCounts[structName] = countVarName
            var_Dependencies[structName] = [countVarName, structName]
            fnparam_pointer = FunctionParameter("win:Struct", structName, "win:count", countVarName)
            fnPrototypes.append(structName, fnparam_pointer)

        allTemplates[templateName] = Template(templateName, fnPrototypes, var_Dependencies, structCounts, arrays)

    return allTemplates

def generateClrallEvents(eventNodes, allTemplates, target_cpp, runtimeFlavor, is_host_windows, write_xplatheader, providerName, inclusionList, generatedFileType):
    clrallEvents = []
    for eventNode in eventNodes:
        eventName = eventNode.getAttribute('symbol')

        if not includeEvent(inclusionList, providerName, eventName):
            continue

        templateName = eventNode.getAttribute('template')

        #generate EventEnabled
        if includeProvider (providerName, runtimeFlavor):
            if not target_cpp:
                clrallEvents.append("static ")
            if generatedFileType == "header-impl":
                clrallEvents.append("inline ")
            clrallEvents.append("%s EventEnabled" % (getEventPipeDataTypeMapping(runtimeFlavor)["BOOL"]))
            clrallEvents.append(eventName)
            clrallEvents.append("(void)")
            if generatedFileType == "header":
                clrallEvents.append(";\n")
            elif generatedFileType == "source-impl-noop":
                clrallEvents.append(" { return 0; }\n")
            else:
                clrallEvents.append(" {return ")
                clrallEvents.append("EventPipeEventEnabled" + eventName + "()")

                if runtimeFlavor.coreclr or write_xplatheader or runtimeFlavor.nativeaot:
                    if not is_host_windows:
                        # native AOT does not support non-windows eventing other than via event pipe
                        if not runtimeFlavor.nativeaot:
                            clrallEvents.append(" || (XplatEventLogger" +
                            ("::" if target_cpp else "_") +
                            "IsEventLoggingEnabled() && EventXplatEnabled" +
                            eventName + "());}\n\n")
                        else:
                            clrallEvents.append(";}\n\n")
                    else:
                        clrallEvents.append(" || EventXplatEnabled" + eventName + "();}\n\n")
                else:
                    clrallEvents.append(";}\n\n")

        #generate FireEtw functions
        fnptype     = []
        fnbody      = []

        if not target_cpp:
            clrallEvents.append("static ")
        if not runtimeFlavor.nativeaot:
            fnptype.append("inline ")
        fnptype.append("%s FireEtw" % (getEventPipeDataTypeMapping(runtimeFlavor)["ULONG"]))

        fnptype.append(eventName)
        fnptype.append("(\n")

        line        = []
        fnptypeline = []

        if templateName:
            template = allTemplates[templateName]
            fnSig = template.signature

            for params in fnSig.paramlist:
                fnparam     = fnSig.getParam(params)
                wintypeName = fnparam.winType
                typewName   = getPalDataTypeMapping(runtimeFlavor)[wintypeName]
                winCount    = fnparam.count
                countw      = getPalDataTypeMapping(runtimeFlavor)[winCount]


                if params in template.structs:
                    fnptypeline.append("%sint %s_ElementSize,\n" % (lindent, params))

                fnptypeline.append(lindent)
                fnptypeline.append(typewName)
                fnptypeline.append(countw)
                fnptypeline.append(" ")
                fnptypeline.append(fnparam.name)
                fnptypeline.append(",\n")

            #fnsignature
            for params in fnSig.paramlist:
                fnparam     = fnSig.getParam(params)

                if params in template.structs:
                    line.append(fnparam.name + "_ElementSize")
                    line.append(", ")

                line.append(fnparam.name)
                line.append(",")

            #remove trailing commas
            if len(line) > 0:
                del line[-1]

        #add activity IDs
        fnptypeline.append(lindent)
        # source file can't have the parameter initializer
        fnptypeline.append("%s ActivityId%s\n" % (getEventPipeDataTypeMapping(runtimeFlavor)["LPCGUID"], " = nullptr," if (target_cpp and (generatedFileType == "header" or generatedFileType == "header-impl")) else ","))
        fnptypeline.append(lindent)
        fnptypeline.append("%s RelatedActivityId" % (getEventPipeDataTypeMapping(runtimeFlavor)["LPCGUID"]))
        if  (target_cpp and (generatedFileType == "header" or generatedFileType == "header-impl")):
            fnptypeline.append(" = nullptr")
        else:
            fnptypeline.append("")

        fnptype.extend(fnptypeline)
        fnptype.append("\n)")
        if generatedFileType == "header":
            fnptype.append(";\n")
            fnptype.append("\n")
        elif generatedFileType == "source-impl-noop":
            fnptype.append("\n{ return ERROR_SUCCESS; }\n\n")
        else: # source-impl
            fnptype.append("\n{\n")
            fnbody.append(lindent)

            fnbody.append("%s status = EventPipeWriteEvent" % (getEventPipeDataTypeMapping(runtimeFlavor)["ULONG"]) + eventName + "(" + ''.join(line))
            if len(line) > 0:
                fnbody.append(",")

            fnbody.append("ActivityId,RelatedActivityId);\n")

            if runtimeFlavor.coreclr or write_xplatheader:
                fnbody.append(lindent)
                fnbody.append("status &= FireEtXplat" + eventName + "(" + ''.join(line) + ");\n")

            if runtimeFlavor.nativeaot:
                if providerName == "Microsoft-Windows-DotNETRuntime" or providerName == "Microsoft-Windows-DotNETRuntimePrivate" or providerName == "Microsoft-Windows-DotNETRuntimeRundown":
                    fnbody.append("#ifndef TARGET_UNIX\n")
                    fnbody.append(lindent)
                    fnbody.append("status &= ")
                else:
                    fnbody.append("return ")
                fnbody.append("FireEtXplat" + eventName + "(" + ''.join(line) + ");\n")
                if providerName == "Microsoft-Windows-DotNETRuntime" or providerName == "Microsoft-Windows-DotNETRuntimePrivate" or providerName == "Microsoft-Windows-DotNETRuntimeRundown":
                    fnbody.append("#endif\n")

            fnbody.append(lindent)
            fnbody.append("return status;\n")
            fnbody.append("}\n\n")

        clrallEvents.extend(fnptype)
        if not (runtimeFlavor.nativeaot and (generatedFileType == "header" or generatedFileType == "source-impl-noop")):
            clrallEvents.extend(fnbody)

    return ''.join(clrallEvents)

def generateClrXplatEvents(eventNodes, allTemplates, extern, runtimeFlavor):
    clrallEvents = []
    for eventNode in eventNodes:
        eventName    = eventNode.getAttribute('symbol')
        templateName = eventNode.getAttribute('template')

        #generate EventEnabled
        if extern: clrallEvents.append('extern "C" ')
        clrallEvents.append("%s EventXplatEnabled" % (getEventPipeDataTypeMapping(runtimeFlavor)["BOOL"]))
        clrallEvents.append(eventName)
        clrallEvents.append("();\n")

        #generate FireEtw functions
        fnptype     = []
        fnptypeline = []
        if extern: fnptype.append('extern "C" ')
        fnptype.append("%s FireEtXplat" % (getEventPipeDataTypeMapping(runtimeFlavor)["ULONG"]))
        fnptype.append(eventName)
        fnptype.append("(\n")

        if templateName:
            template = allTemplates[templateName]
            fnSig = template.signature

            for params in fnSig.paramlist:
                fnparam     = fnSig.getParam(params)
                wintypeName = fnparam.winType
                typewName   = getPalDataTypeMapping(runtimeFlavor)[wintypeName]
                winCount    = fnparam.count
                countw      = getPalDataTypeMapping(runtimeFlavor)[winCount]


                if params in template.structs:
                    fnptypeline.append("%sint %s_ElementSize,\n" % (lindent, params))

                fnptypeline.append(lindent)
                fnptypeline.append(typewName)
                fnptypeline.append(countw)
                fnptypeline.append(" ")
                fnptypeline.append(fnparam.name)
                fnptypeline.append(",\n")

            #remove trailing commas
            if len(fnptypeline) > 0:
                del fnptypeline[-1]

        fnptype.extend(fnptypeline)
        fnptype.append("\n);\n")
        clrallEvents.extend(fnptype)

    return ''.join(clrallEvents)

def generateClrEventPipeWriteEvents(eventNodes, allTemplates, extern, target_cpp, runtimeFlavor, providerName, inclusion_list):
    clrallEvents = []
    for eventNode in eventNodes:
        eventName    = eventNode.getAttribute('symbol')
        if not includeEvent(inclusion_list, providerName, eventName):
            continue

        templateName = eventNode.getAttribute('template')

        #generate EventPipeEventEnabled and EventPipeWriteEvent functions
        eventenabled = []
        writeevent   = []
        fnptypeline  = []

        if extern:eventenabled.append('extern "C" ')
        eventenabled.append("%s EventPipeEventEnabled" % (getEventPipeDataTypeMapping(runtimeFlavor)["BOOL"]))
        eventenabled.append(eventName)
        eventenabled.append("(void);\n")

        if extern: writeevent.append('extern "C" ')
        writeevent.append("%s EventPipeWriteEvent" % (getEventPipeDataTypeMapping(runtimeFlavor)["ULONG"]))
        writeevent.append(eventName)
        writeevent.append("(\n")

        if templateName:
            template = allTemplates[templateName]
            fnSig    = template.signature

            for params in fnSig.paramlist:
                fnparam     = fnSig.getParam(params)
                wintypeName = fnparam.winType
                typewName   = getPalDataTypeMapping(runtimeFlavor)[wintypeName]
                winCount    = fnparam.count
                countw      = getPalDataTypeMapping(runtimeFlavor)[winCount]

                if params in template.structs:
                    fnptypeline.append("%sint %s_ElementSize,\n" % (lindent, params))

                fnptypeline.append(lindent)
                fnptypeline.append(typewName)
                fnptypeline.append(countw)
                fnptypeline.append(" ")
                fnptypeline.append(fnparam.name)
                fnptypeline.append(",\n")

        fnptypeline.append(lindent)
        fnptypeline.append("%s ActivityId%s\n" % (getEventPipeDataTypeMapping(runtimeFlavor)["LPCGUID"], " = nullptr," if target_cpp else ","))
        fnptypeline.append(lindent)
        fnptypeline.append("%s RelatedActivityId%s" % (getEventPipeDataTypeMapping(runtimeFlavor)["LPCGUID"], " = nullptr" if target_cpp else ""))

        writeevent.extend(fnptypeline)
        writeevent.append("\n);\n")
        clrallEvents.extend(eventenabled)
        clrallEvents.extend(writeevent)

    return ''.join(clrallEvents)

#generates the dummy header file which is used by the VM as entry point to the logging Functions
def generateclrEtwDummy(eventNodes,allTemplates):
    clretmEvents = []
    for eventNode in eventNodes:
        eventName    = eventNode.getAttribute('symbol')
        templateName = eventNode.getAttribute('template')

        fnptype     = []
        #generate FireEtw functions
        fnptype.append("#define FireEtw")
        fnptype.append(eventName)
        fnptype.append("(");
        line        = []
        if templateName:
            template = allTemplates[templateName]
            fnSig = template.signature

            for params in fnSig.paramlist:
                fnparam     = fnSig.getParam(params)

                if params in template.structs:
                    line.append(fnparam.name + "_ElementSize")
                    line.append(", ")

                line.append(fnparam.name)
                line.append(", ")

            #remove trailing commas
            if len(line) > 0:
                del line[-1]

        fnptype.extend(line)
        fnptype.append(") 0\n")
        clretmEvents.extend(fnptype)

    return ''.join(clretmEvents)

def generateEtmDummyHeader(sClrEtwAllMan,clretwdummy):

    if not clretwdummy:
        return

    tree           = DOM.parse(sClrEtwAllMan)

    incDir = os.path.dirname(os.path.realpath(clretwdummy))
    if not os.path.exists(incDir):
        os.makedirs(incDir)

    with open_for_update(clretwdummy) as Clretwdummy:
        Clretwdummy.write(stdprolog + "\n")

        for providerNode in tree.getElementsByTagName('provider'):
            templateNodes = providerNode.getElementsByTagName('template')
            allTemplates  = parseTemplateNodes(templateNodes)
            eventNodes = providerNode.getElementsByTagName('event')
            #pal: create etmdummy.h
            Clretwdummy.write(generateclrEtwDummy(eventNodes, allTemplates) + "\n")

def convertToLevelId(level):
    if level == "win:LogAlways":
       return 0
    if level == "win:Critical":
       return 1
    if level == "win:Error":
       return 2
    if level == "win:Warning":
       return 3
    if level == "win:Informational":
       return 4
    if level == "win:Verbose":
       return 5
    raise Exception("unknown level " + level)

def getKeywordsMaskCombined(keywords, keywordsToMask):
    mask = 0
    for keyword in keywords.split(" "):
       if keyword == "":
          continue
       mask |= keywordsToMask[keyword]

    return mask

def updateclreventsfile(write_xplatheader, target_cpp, runtimeFlavor, is_host_windows, eventpipe_trace_context_typedef, dotnet_trace_context_typedef_windows, tree, clrallevents, inclusion_list, generatedFileType):
    with open_for_update(clrallevents) as Clrallevents:
        Clrallevents.write(stdprolog)
        Clrallevents.write('#include <minipal/guid.h>\n\n')
        if generatedFileType=="header-impl":
            if runtimeFlavor.mono:
                Clrallevents.write(getCoreCLRMonoNativeAotTypeAdaptionDefines() + "\n")
            if runtimeFlavor.coreclr or write_xplatheader:
                Clrallevents.write('#include "clrxplatevents.h"\n')
            Clrallevents.write('#include "clreventpipewriteevents.h"\n')
        elif generatedFileType == "header":
            Clrallevents.write('#ifndef CLR_ETW_ALL_MAIN_H\n')
            Clrallevents.write('#define CLR_ETW_ALL_MAIN_H\n\n')
        elif generatedFileType == "source-impl":
            Clrallevents.write('#include <common.h>\n')
            Clrallevents.write('#include <Pal.h>\n')
            Clrallevents.write('#include "clretwallmain.h"\n')
            Clrallevents.write('#include "clreventpipewriteevents.h"\n')
            Clrallevents.write('#ifdef FEATURE_ETW\n')
            Clrallevents.write('#include "ClrEtwAll.h"\n')
            Clrallevents.write('#endif\n')
            Clrallevents.write('\n')
        elif generatedFileType == "source-impl-noop":
            Clrallevents.write('#include <CommonTypes.h>\n')
            Clrallevents.write('#include <CommonMacros.h>\n\n')
            Clrallevents.write('#include <Pal.h>\n\n')
            Clrallevents.write('#ifndef ERROR_SUCCESS\n')
            Clrallevents.write('#define ERROR_SUCCESS 0L\n')
            Clrallevents.write('#endif\n\n')

        # define DOTNET_TRACE_CONTEXT depending on the platform
        if is_host_windows and not runtimeFlavor.nativeaot:
            Clrallevents.write(eventpipe_trace_context_typedef)  # define EVENTPIPE_TRACE_CONTEXT
            if runtimeFlavor.coreclr or write_xplatheader:
                Clrallevents.write(dotnet_trace_context_typedef_windows + "\n")
            else:
                Clrallevents.write("\n")

        if not is_host_windows and not write_xplatheader and not runtimeFlavor.nativeaot:
            Clrallevents.write(eventpipe_trace_context_typedef)  # define EVENTPIPE_TRACE_CONTEXT
            Clrallevents.write("\n")

        # Hack to write etw specific information to nativeaot
        for providerNode in tree.getElementsByTagName('provider'):
            providerName = providerNode.getAttribute('name')
            templateNodes = providerNode.getElementsByTagName('template')
            allTemplates  = parseTemplateNodes(templateNodes)
            eventNodes = providerNode.getElementsByTagName('event')

            #vm header:
            Clrallevents.write(generateClrallEvents(eventNodes, allTemplates, target_cpp, runtimeFlavor, is_host_windows, write_xplatheader, providerName, inclusion_list, generatedFileType))

            providerName = providerNode.getAttribute('name')
            providerSymbol = providerNode.getAttribute('symbol')

            eventpipeProviderCtxName = providerSymbol + "_EVENTPIPE_Context"
            if is_host_windows and not (write_xplatheader or runtimeFlavor.nativeaot):
                Clrallevents.write(('constexpr ' if target_cpp else 'static const ') + 'EVENTPIPE_TRACE_CONTEXT ' + eventpipeProviderCtxName + ' = { W("' + providerName + '"), 0, false, 0 };\n')

            if not is_host_windows and not write_xplatheader and not runtimeFlavor.nativeaot:
                Clrallevents.write('__attribute__((weak)) EVENTPIPE_TRACE_CONTEXT ' + eventpipeProviderCtxName + ' = { W("' + providerName + '"), 0, false, 0 };\n')

        if generatedFileType == "header":
            Clrallevents.write("#endif // __CLR_ETW_ALL_MAIN_H__\n")

def generatePlatformIndependentFiles(sClrEtwAllMan, incDir, etmDummyFile, extern, write_xplatheader, target_cpp, runtimeFlavor, is_host_windows, inclusion_list):

    generateEtmDummyHeader(sClrEtwAllMan,etmDummyFile)
    tree           = DOM.parse(sClrEtwAllMan)

    if not incDir:
        return

    if not os.path.exists(incDir):
        os.makedirs(incDir)

    eventpipe_trace_context_typedef = """
#if !defined(EVENTPIPE_TRACE_CONTEXT_DEF)
#define EVENTPIPE_TRACE_CONTEXT_DEF
typedef struct _EVENTPIPE_TRACE_CONTEXT
{
    const %s * Name;
    %s Level;
    bool IsEnabled;
    %s EnabledKeywordsBitmask;
} EVENTPIPE_TRACE_CONTEXT, *PEVENTPIPE_TRACE_CONTEXT;
#endif // EVENTPIPE_TRACE_CONTEXT_DEF
""" % (getEventPipeDataTypeMapping(runtimeFlavor)["WCHAR"], getEventPipeDataTypeMapping(runtimeFlavor)["UCHAR"], getEventPipeDataTypeMapping(runtimeFlavor)["ULONGLONG"])

    lttng_trace_context_typedef = """
#if !defined(LTTNG_TRACE_CONTEXT_DEF)
#define LTTNG_TRACE_CONTEXT_DEF
typedef struct _LTTNG_TRACE_CONTEXT
{
    WCHAR const * Name;
    UCHAR Level;
    bool IsEnabled;
    ULONGLONG EnabledKeywordsBitmask;
} LTTNG_TRACE_CONTEXT, *PLTTNG_TRACE_CONTEXT;
#endif // LTTNG_TRACE_CONTEXT_DEF
"""

    dotnet_trace_context_typedef_windows = """
#if !defined(DOTNET_TRACE_CONTEXT_DEF)
#define DOTNET_TRACE_CONTEXT_DEF
typedef struct _DOTNET_TRACE_CONTEXT
{
    PMCGEN_TRACE_CONTEXT EtwProvider;
    EVENTPIPE_TRACE_CONTEXT EventPipeProvider;
} DOTNET_TRACE_CONTEXT, *PDOTNET_TRACE_CONTEXT;
#endif // DOTNET_TRACE_CONTEXT_DEF
"""

    dotnet_trace_context_typedef_unix = """
#if !defined(DOTNET_TRACE_CONTEXT_DEF)
#define DOTNET_TRACE_CONTEXT_DEF
typedef struct _DOTNET_TRACE_CONTEXT
{
    EVENTPIPE_TRACE_CONTEXT EventPipeProvider;
    PLTTNG_TRACE_CONTEXT LttngProvider;
} DOTNET_TRACE_CONTEXT, *PDOTNET_TRACE_CONTEXT;
#endif // DOTNET_TRACE_CONTEXT_DEF
"""

    # Write the main source(s) for FireETW* functions
    # nativeaot requires header and source file to be separated as well as a noop implementation
    if runtimeFlavor.nativeaot:
        updateclreventsfile(write_xplatheader, target_cpp, runtimeFlavor, is_host_windows, eventpipe_trace_context_typedef, dotnet_trace_context_typedef_windows, tree, os.path.join(incDir, "clretwallmain.cpp"), inclusion_list, "source-impl")
        updateclreventsfile(write_xplatheader, target_cpp, runtimeFlavor, is_host_windows, eventpipe_trace_context_typedef, dotnet_trace_context_typedef_windows, tree, os.path.join(incDir, "clretwallmain.h"), inclusion_list, "header")
        updateclreventsfile(write_xplatheader, target_cpp, runtimeFlavor, is_host_windows, eventpipe_trace_context_typedef, dotnet_trace_context_typedef_windows, tree, os.path.join(incDir, "disabledclretwallmain.cpp"), inclusion_list, "source-impl-noop")
    else:
        updateclreventsfile(write_xplatheader, target_cpp, runtimeFlavor, is_host_windows, eventpipe_trace_context_typedef, dotnet_trace_context_typedef_windows, tree, os.path.join(incDir, "clretwallmain.h"), inclusion_list, "header-impl")

    if write_xplatheader:
        clrproviders = os.path.join(incDir, "clrproviders.h")
        with open_for_update(clrproviders) as Clrproviders:
            Clrproviders.write("""
typedef struct _EVENT_DESCRIPTOR
{
    int const Level;
    ULONGLONG const Keyword;
} EVENT_DESCRIPTOR;
""")
            if not is_host_windows and not runtimeFlavor.nativeaot:
                Clrproviders.write(eventpipe_trace_context_typedef)  # define EVENTPIPE_TRACE_CONTEXT
                Clrproviders.write(lttng_trace_context_typedef)  # define LTTNG_TRACE_CONTEXT
                Clrproviders.write(dotnet_trace_context_typedef_unix + "\n")

            allProviders = []
            nbProviders = 0
            for providerNode in tree.getElementsByTagName('provider'):
                keywords = []
                keywordsToMask = {}
                providerName = str(providerNode.getAttribute('name'))
                providerSymbol = str(providerNode.getAttribute('symbol'))
                nbProviders += 1
                nbKeywords = 0
                if not is_host_windows and not runtimeFlavor.nativeaot:
                    eventpipeProviderCtxName = providerSymbol + "_EVENTPIPE_Context"
                    Clrproviders.write('__attribute__((weak)) EVENTPIPE_TRACE_CONTEXT ' + eventpipeProviderCtxName + ' = { W("' + providerName + '"), 0, false, 0 };\n')
                    lttngProviderCtxName = providerSymbol + "_LTTNG_Context"
                    Clrproviders.write('__attribute__((weak)) LTTNG_TRACE_CONTEXT ' + lttngProviderCtxName + ' = { W("' + providerName + '"), 0, false, 0 };\n')

                Clrproviders.write("// Keywords\n");
                for keywordNode in providerNode.getElementsByTagName('keyword'):
                    keywordName = keywordNode.getAttribute('name')
                    keywordMask = keywordNode.getAttribute('mask')
                    keywordSymbol = keywordNode.getAttribute('symbol')
                    Clrproviders.write("#define " + keywordSymbol + " " + keywordMask + "\n")

                    keywords.append("{ \"" + keywordName + "\", " + keywordMask + " }")
                    keywordsToMask[keywordName] = int(keywordMask, 16)
                    nbKeywords += 1

                for eventNode in providerNode.getElementsByTagName('event'):
                    levelName = eventNode.getAttribute('level')
                    symbolName = eventNode.getAttribute('symbol')
                    keywords = eventNode.getAttribute('keywords')
                    level = convertToLevelId(levelName)
                    Clrproviders.write(("constexpr " if target_cpp else "static const ") + "EVENT_DESCRIPTOR " + symbolName + " = { " + str(level) + ", " + hex(getKeywordsMaskCombined(keywords, keywordsToMask)) + " };\n")

                allProviders.append("&" + providerSymbol + "_LTTNG_Context")

            # define and initialize runtime providers' DOTNET_TRACE_CONTEXT depending on the platform
            if not is_host_windows and not runtimeFlavor.nativeaot:
                Clrproviders.write('#define NB_PROVIDERS ' + str(nbProviders) + '\n')
                Clrproviders.write(('constexpr ' if target_cpp else 'static const ') + 'LTTNG_TRACE_CONTEXT * ALL_LTTNG_PROVIDERS_CONTEXT[NB_PROVIDERS] = { ')
                Clrproviders.write(', '.join(allProviders))
                Clrproviders.write(' };\n')


    clreventpipewriteevents = os.path.join(incDir, "clreventpipewriteevents.h")
    with open_for_update(clreventpipewriteevents) as Clreventpipewriteevents:
        Clreventpipewriteevents.write(stdprolog + "\n")

        for providerNode in tree.getElementsByTagName('provider'):
            providerName = providerNode.getAttribute('name')
            templateNodes = providerNode.getElementsByTagName('template')
            allTemplates  = parseTemplateNodes(templateNodes)
            eventNodes = providerNode.getElementsByTagName('event')

            #eventpipe: create clreventpipewriteevents.h
            Clreventpipewriteevents.write(generateClrEventPipeWriteEvents(eventNodes, allTemplates, extern, target_cpp, runtimeFlavor, providerName, inclusion_list) + "\n")

    # Write secondary headers for FireEtXplat* and EventPipe* functions
    if write_xplatheader:
        clrxplatevents = os.path.join(incDir, "clrxplatevents.h")
        with open_for_update(clrxplatevents) as Clrxplatevents:
            Clrxplatevents.write(stdprolog + "\n")

            for providerNode in tree.getElementsByTagName('provider'):
                templateNodes = providerNode.getElementsByTagName('template')
                allTemplates  = parseTemplateNodes(templateNodes)
                eventNodes = providerNode.getElementsByTagName('event')

                #pal: create clrallevents.h
                Clrxplatevents.write(generateClrXplatEvents(eventNodes, allTemplates, extern, runtimeFlavor) + "\n")

import argparse
import sys

def main(argv):

    #parse the command line
    parser = argparse.ArgumentParser(description="Generates the Code required to instrument LTTtng logging mechanism")

    required = parser.add_argument_group('required arguments')
    required.add_argument('--man',  type=str, required=True,
                                    help='full path to manifest containing the description of events')
    required.add_argument('--incdir',  type=str, default=None,
                                    help='full path to directory where the header files will be generated')
    required.add_argument('--inc',  type=str,default="",
                                    help='full path to inclusion list')
    required.add_argument('--dummy',  type=str,default=None,
                                    help='full path to file that will have dummy definitions of FireEtw functions')
    required.add_argument('--runtimeflavor', type=str,default="CoreCLR",
                                    help='runtime flavor'),
    required.add_argument('--targetos', type=str,default=None),
    required.add_argument('--nonextern', action='store_true',
                                    help='if specified, will not generated extern function stub headers' )
    required.add_argument('--noxplatheader', action='store_true',
                                    help='if specified, will not write a generated cross-platform header' )
    args, unknown = parser.parse_known_args(argv)
    if unknown:
        print('Unknown argument(s): ', ', '.join(unknown))
        return 1

    sClrEtwAllMan     = args.man
    incdir            = args.incdir
    inclusion_filename = args.inc
    etmDummyFile      = args.dummy
    runtimeFlavor     = RuntimeFlavor(args.runtimeflavor)
    extern            = not args.nonextern
    write_xplatheader = not args.noxplatheader
    targetOS          = args.targetos

    if targetOS is None:
        if os.name == "nt":
            targetOS = "windows"

    is_host_windows = targetOS == "windows"
    target_cpp = True
    if runtimeFlavor.mono:
        extern = False
        target_cpp = False
        write_xplatheader = False

    inclusion_list = parseInclusionList(inclusion_filename)

    generatePlatformIndependentFiles(sClrEtwAllMan, incdir, etmDummyFile, extern, write_xplatheader, target_cpp, runtimeFlavor, is_host_windows, inclusion_list)

if __name__ == '__main__':
    return_code = main(sys.argv[1:])
    sys.exit(return_code)
